/*-------------------------------------------------
 *
 * OrbitsWriter - An Offline Blog Writer
 *
 * Copyright (C) 2016  devbean@galaxyworld.org
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 *-------------------------------------------------*/

#include "sourceeditor.h"

#include "appcontext.h"
#include "document/markupgenerator.h"

#include <QApplication>
#include <QDebug>
#include <QPainter>
#include <QSettings>
#include <QSyntaxHighlighter>

using namespace Core;
using namespace Core::Internal;

//---------- SourceEditorPrivate ----------//

SourceEditorPrivate::SourceEditorPrivate(SourceEditor *se)
    : QObject(se),
      q(se),
      highlighter(nullptr),
      lineNumberArea(new LineNumberArea(q))
{
    updateLineNumberAreaWidth(0);
    highlightCurrentLine();
}

SourceEditorPrivate::~SourceEditorPrivate()
{
}

void SourceEditorPrivate::lineNumberAreaPaintEvent(QPaintEvent *event)
{
    QPainter painter(lineNumberArea);
    painter.fillRect(event->rect(), Qt::lightGray);

    QTextBlock block = q->firstVisibleBlock();
    int blockNumber = block.blockNumber();
    int top = (int) q->blockBoundingGeometry(block).translated(q->contentOffset()).top();
    int bottom = top + (int) q->blockBoundingRect(block).height();

    while (block.isValid() && top <= event->rect().bottom()) {
        if (block.isVisible() && bottom >= event->rect().top()) {
            QString number = QString::number(blockNumber + 1);
            painter.setPen(Qt::black);
            painter.drawText(0, top, lineNumberArea->width(), q->fontMetrics().height(),
                             Qt::AlignRight, number);
        }

        block = block.next();
        top = bottom;
        bottom = top + (int) q->blockBoundingRect(block).height();
        ++blockNumber;
    }
}

int SourceEditorPrivate::lineNumberAreaWidth()
{
    int digits = 1;
    int max = qMax(1, q->blockCount());
    while (max >= 10) {
        max /= 10;
        ++digits;
    }

    int space = 3 + q->fontMetrics().width(QLatin1Char('9')) * digits;
    return space;
}

void SourceEditorPrivate::updateLineNumberAreaWidth(int)
{
    q->setViewportMargins(lineNumberAreaWidth(), 0, 0, 0);
}

void SourceEditorPrivate::highlightCurrentLine()
{
    QList<QTextEdit::ExtraSelection> extraSelections;

    if (!q->isReadOnly()) {
        QTextEdit::ExtraSelection selection;

        QColor lineColor = QColor(Qt::yellow).lighter(160);

        selection.format.setBackground(lineColor);
        selection.format.setProperty(QTextFormat::FullWidthSelection, true);
        selection.cursor = q->textCursor();
        selection.cursor.clearSelection();
        extraSelections.append(selection);
    }

    q->setExtraSelections(extraSelections);
}

void SourceEditorPrivate::updateLineNumberArea(const QRect &rect, int dy)
{
    if (dy) {
        lineNumberArea->scroll(0, dy);
    } else {
        lineNumberArea->update(0, rect.y(), lineNumberArea->width(), rect.height());
    }

    if (rect.contains(q->viewport()->rect())) {
        updateLineNumberAreaWidth(0);
    }
}

static const char * const GROUP_SOURCEEDITOR = "SourceEditor";
static const char * const SOURCEEDITOR_FONT  = "font";

void SourceEditorPrivate::onAppContextAboutToExit()
{
    AppContext *ctx = AppContext::instance();
    QSettings *settings = ctx->settings();

    settings->beginGroup(QLatin1String(GROUP_SOURCEEDITOR));
    settings->setValue(QLatin1String(SOURCEEDITOR_FONT), q->font());
    settings->endGroup();
}

void SourceEditorPrivate::onMainWindowShown()
{
    AppContext *ctx = AppContext::instance();
    QSettings *settings = ctx->settings();

    settings->beginGroup(QLatin1String(GROUP_SOURCEEDITOR));
    QFont font("Courier New", 11);
    if (settings->contains(QLatin1String(SOURCEEDITOR_FONT))) {
        QVariant fontValue = settings->value(QLatin1String(SOURCEEDITOR_FONT));
        font = fontValue.value<QFont>();
    }
    q->setFont(font);
    settings->endGroup();
}

//---------- LineNumberArea ----------//

LineNumberArea::LineNumberArea(SourceEditor *editor)
    : QWidget(editor),
      sourceEditor(editor)
{

}

QSize LineNumberArea::sizeHint() const
{
    return QSize(sourceEditor->d->lineNumberAreaWidth(), 0);
}

void LineNumberArea::paintEvent(QPaintEvent *event)
{
    sourceEditor->d->lineNumberAreaPaintEvent(event);
}

//---------- SourceEditor ----------//

SourceEditor::SourceEditor(MarkupBuilder *builder, QWidget *parent)
    : QPlainTextEdit(parent),
      d(new SourceEditorPrivate(this))
{
    AppContext *ctx = AppContext::instance();

    d->builder = builder;
    d->highlighter = ctx->highlighter(d->builder);
    if (d->highlighter) {
        d->highlighter->setDocument(document());
    }
    connect(ctx, &AppContext::mainWindowShown, d, &SourceEditorPrivate::onMainWindowShown);
    connect(ctx, &AppContext::appContextAboutToExit, d, &SourceEditorPrivate::onAppContextAboutToExit);

    setStyleSheet("border: 0");

    connect(this, &QPlainTextEdit::blockCountChanged, d, &SourceEditorPrivate::updateLineNumberAreaWidth);
    connect(this, &QPlainTextEdit::updateRequest, d, &SourceEditorPrivate::updateLineNumberArea);
    connect(this, &QPlainTextEdit::cursorPositionChanged, d, &SourceEditorPrivate::highlightCurrentLine);
}

SourceEditor::~SourceEditor()
{
}

void SourceEditor::updateSource(QTextDocument *document)
{
    MarkupGenerator gen(d->builder);
    gen.processDocument(document);
    setPlainText(d->builder->result());
}

void SourceEditor::resizeEvent(QResizeEvent *event)
{
    QPlainTextEdit::resizeEvent(event);

    QRect cr = contentsRect();
    d->lineNumberArea->setGeometry(QRect(cr.left(), cr.top(), d->lineNumberAreaWidth(), cr.height()));
}
